#include "networkapi_curve25519.h"

typedef struct
{
    unsigned int v[32];
}networkapi_pre_secure;

static void networkapi_pre_secure_setzero(networkapi_pre_secure *r)
{
    unsigned char i;
    for(i=0;i<32;i++)
        r->v[i]=0;
}

static void networkapi_pre_secure_cmov(networkapi_pre_secure *r, const networkapi_pre_secure *x, unsigned char b)
{
    unsigned char i;
    unsigned long mask = b;
    mask = -mask;
    for(i=0;i<32;i++)
        r->v[i] ^= mask & (x->v[i] ^ r->v[i]);
}

static unsigned int times19(unsigned int a)
{
    return (a << 4) + (a << 1) + a;
}

static unsigned int times38(unsigned int a)
{
    return (a << 5) + (a << 2) + (a << 1);
}

static void reduce_add_sub(networkapi_pre_secure *r)
{
    unsigned int t;
    int i,rep;
    
    for(rep=0;rep<4;rep++)
    {
        t = r->v[31] >> 7;
        r->v[31] &= 127;
        t = times19(t);
        r->v[0] += t;
        for(i=0;i<31;i++)
        {
            t = r->v[i] >> 8;
            r->v[i+1] += t;
            r->v[i] &= 255;
        }
    }
}

static void reduce_mul(networkapi_pre_secure *r)
{
    unsigned int t;
    int i,rep;
    
    for(rep=0;rep<2;rep++)
    {
        t = r->v[31] >> 7;
        r->v[31] &= 127;
        t = times19(t);
        r->v[0] += t;
        for(i=0;i<31;i++)
        {
            t = r->v[i] >> 8;
            r->v[i+1] += t;
            r->v[i] &= 255;
        }
    }
}

static void networkapi_pre_secure_add(networkapi_pre_secure *r, const networkapi_pre_secure *x, const networkapi_pre_secure *y)
{
    int i;
    for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i];
    reduce_add_sub(r);
}

static void networkapi_pre_secure_sub(networkapi_pre_secure *r, const networkapi_pre_secure *x, const networkapi_pre_secure *y)
{
    int i;
    unsigned int t[32];
    t[0] = x->v[0] + 0x1da;
    t[31] = x->v[31] + 0xfe;
    for(i=1;i<31;i++) t[i] = x->v[i] + 0x1fe;
    for(i=0;i<32;i++) r->v[i] = t[i] - y->v[i];
    reduce_add_sub(r);
}

static void networkapi_pre_secure_mul(networkapi_pre_secure *r, const networkapi_pre_secure *x, const networkapi_pre_secure *y)
{
    int i,j;
    unsigned int t[63];
    for(i=0;i<63;i++)t[i] = 0;
    
    for(i=0;i<32;i++)
        for(j=0;j<32;j++)
            t[i+j] += x->v[i] * y->v[j];
    
    for(i=32;i<63;i++)
        r->v[i-32] = t[i-32] + times38(t[i]);
    r->v[31] = t[31]; /* result now in r[0]...r[31] */
    
    reduce_mul(r);
}

static void networkapi_pre_secure_square(networkapi_pre_secure *r, const networkapi_pre_secure *x)
{
    networkapi_pre_secure_mul(r, x, x);
    //  unsigned char t[64];
    //  bigint_square256(t,x->v);
    //  networkapi_pre_secure_red(r,t);
}

static const networkapi_pre_secure _121666 = {{0x42, 0xDB, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
static void networkapi_pre_secure_mul121666(networkapi_pre_secure *r, const networkapi_pre_secure *x)
{
    networkapi_pre_secure_mul(r, x, &_121666);
    //  unsigned char t[64];
    //  //bigint_mul256(t,x->v,_121666.v);
    //  bigint_mul121666(t,x->v);
    //  networkapi_pre_secure_red(r,t);
}

static void networkapi_pre_secure_setone(networkapi_pre_secure *r)
{
    unsigned char i;
    r->v[0] = 1;
    for(i=1;i<32;i++)
        r->v[i]=0;
}


static void networkapi_pre_secure_unpack(networkapi_pre_secure *r, const unsigned char x[32])
{
    unsigned char i;
    for(i=0;i<32;i++)
        r->v[i] = x[i];
    r->v[31] &= 127;
}

static unsigned int equal(unsigned int a,unsigned int b) /* 16-bit inputs */
{
    unsigned int x = a ^ b; /* 0: yes; 1..65535: no */
    x -= 1; /* 4294967295: yes; 0..65534: no */
    x >>= 31; /* 1: yes; 0: no */
    return x;
}

static unsigned int ge(unsigned int a,unsigned int b) /* 16-bit inputs */
{
    unsigned int x = a;
    x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */
    x >>= 31; /* 0: yes; 1: no */
    x ^= 1; /* 1: yes; 0: no */
    return x;
}

/* reduction modulo 2^255-19 */
static void networkapi_pre_secure_freeze(networkapi_pre_secure *r)
{
    int i;
    unsigned int m = equal(r->v[31],127);
    for(i=30;i>0;i--)
        m &= equal(r->v[i],255);
    m &= ge(r->v[0],237);
    
    m = -m;
    
    r->v[31] -= m&127;
    for(i=30;i>0;i--)
        r->v[i] -= m&255;
    r->v[0] -= m&237;
}

/* Assumes input x being reduced below 2^255 */
static void networkapi_pre_secure_pack(unsigned char r[32], const networkapi_pre_secure *x)
{
    unsigned char i;
    networkapi_pre_secure y = *x;
    networkapi_pre_secure_freeze(&y);
    for(i=0;i<32;i++)
        r[i] = y.v[i];
}

static void work_cswap(networkapi_pre_secure *work, char b)
{
  networkapi_pre_secure t;
  networkapi_pre_secure_setzero(&t);
  networkapi_pre_secure_cmov(&t, work+1, b);
  networkapi_pre_secure_cmov(work+1, work+3, b);
  networkapi_pre_secure_cmov(work+3, &t, b);
  networkapi_pre_secure_cmov(&t, work+2, b);
  networkapi_pre_secure_cmov(work+2, work+4, b);
  networkapi_pre_secure_cmov(work+4, &t, b);
}

//static const networkapi_pre_secure _121666 = {{0x42, 0xDB, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};

static void ladderstep(networkapi_pre_secure *work)
{
  networkapi_pre_secure t[2];
  networkapi_pre_secure *t1 = &t[0];
  networkapi_pre_secure *t2 = &t[1];
  networkapi_pre_secure *x0 = work;
  networkapi_pre_secure *xp = work+1;
  networkapi_pre_secure *zp = work+2;
  networkapi_pre_secure *xq = work+3;
  networkapi_pre_secure *zq = work+4;

  networkapi_pre_secure_add(t1, xq, zq);
  networkapi_pre_secure_sub(xq, xq, zq);
  networkapi_pre_secure_add(zq, xp, zp);
  networkapi_pre_secure_sub(xp, xp, zp);
  networkapi_pre_secure_mul(t1, t1, xp);
  networkapi_pre_secure_mul(xq, xq, zq);
  networkapi_pre_secure_square(zq, zq);
  networkapi_pre_secure_square(xp, xp);
  networkapi_pre_secure_sub(t2, zq, xp);
  networkapi_pre_secure_mul121666(zp, t2);
  networkapi_pre_secure_add(zp, zp, xp);
  networkapi_pre_secure_mul(zp, t2, zp);
  networkapi_pre_secure_mul(xp, zq, xp);
  networkapi_pre_secure_sub(zq, t1, xq);
  networkapi_pre_secure_square(zq, zq);
  networkapi_pre_secure_mul(zq, zq, x0);
  networkapi_pre_secure_add(xq, t1, xq);
  networkapi_pre_secure_square(xq, xq);
}

static void networkapi_pre_secure_invert(networkapi_pre_secure *r, const networkapi_pre_secure *x)
{
    networkapi_pre_secure z2;
    networkapi_pre_secure z11;
    networkapi_pre_secure z2_10_0;
    networkapi_pre_secure z2_50_0;
    networkapi_pre_secure z2_100_0;
    networkapi_pre_secure t0;
    networkapi_pre_secure t1;
    unsigned char i;
    
    /* 2 */ networkapi_pre_secure_square(&z2,x);
    /* 4 */ networkapi_pre_secure_square(&t1,&z2);
    /* 8 */ networkapi_pre_secure_square(&t0,&t1);
    /* 9 */ networkapi_pre_secure_mul(&z2_10_0,&t0,x);
    /* 11 */ networkapi_pre_secure_mul(&z11,&z2_10_0,&z2);
    /* 22 */ networkapi_pre_secure_square(&t0,&z11);
    /* 2^5 - 2^0 = 31 */ networkapi_pre_secure_mul(&z2_10_0,&t0,&z2_10_0);
    
    /* 2^6 - 2^1 */ networkapi_pre_secure_square(&t0,&z2_10_0);
    /* 2^7 - 2^2 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^8 - 2^3 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^9 - 2^4 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^10 - 2^5 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^10 - 2^0 */ networkapi_pre_secure_mul(&z2_10_0,&t0,&z2_10_0);
    
    /* 2^11 - 2^1 */ networkapi_pre_secure_square(&t0,&z2_10_0);
    /* 2^12 - 2^2 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { networkapi_pre_secure_square(&t0,&t1); networkapi_pre_secure_square(&t1,&t0); }
    /* 2^20 - 2^0 */ networkapi_pre_secure_mul(&z2_50_0,&t1,&z2_10_0);
    
    /* 2^21 - 2^1 */ networkapi_pre_secure_square(&t0,&z2_50_0);
    /* 2^22 - 2^2 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { networkapi_pre_secure_square(&t0,&t1); networkapi_pre_secure_square(&t1,&t0); }
    /* 2^40 - 2^0 */ networkapi_pre_secure_mul(&t0,&t1,&z2_50_0);
    /* 2^41 - 2^1 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^42 - 2^2 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { networkapi_pre_secure_square(&t1,&t0); networkapi_pre_secure_square(&t0,&t1); }
    /* 2^50 - 2^0 */ networkapi_pre_secure_mul(&z2_50_0,&t0,&z2_10_0);
    
    /* 2^51 - 2^1 */ networkapi_pre_secure_square(&t0,&z2_50_0);
    /* 2^52 - 2^2 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { networkapi_pre_secure_square(&t0,&t1); networkapi_pre_secure_square(&t1,&t0); }
    /* 2^100 - 2^0 */ networkapi_pre_secure_mul(&z2_100_0,&t1,&z2_50_0);
    
    /* 2^101 - 2^1 */ networkapi_pre_secure_square(&t1,&z2_100_0);
    /* 2^102 - 2^2 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { networkapi_pre_secure_square(&t1,&t0); networkapi_pre_secure_square(&t0,&t1); }
    /* 2^200 - 2^0 */ networkapi_pre_secure_mul(&t1,&t0,&z2_100_0);
    
    /* 2^201 - 2^1 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^202 - 2^2 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { networkapi_pre_secure_square(&t0,&t1); networkapi_pre_secure_square(&t1,&t0); }
    /* 2^250 - 2^0 */ networkapi_pre_secure_mul(&t0,&t1,&z2_50_0);
    
    /* 2^251 - 2^1 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^252 - 2^2 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^253 - 2^3 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^254 - 2^4 */ networkapi_pre_secure_square(&t0,&t1);
    /* 2^255 - 2^5 */ networkapi_pre_secure_square(&t1,&t0);
    /* 2^255 - 21 */ networkapi_pre_secure_mul(r,&t1,&z11);
}

static void mladder(networkapi_pre_secure *xr, networkapi_pre_secure *zr, const unsigned char s[32])
{
  networkapi_pre_secure work[5];
  unsigned char bit, prevbit=0;
  unsigned char swap;
  signed char j;
  signed char i;

  work[0] = *xr;
  networkapi_pre_secure_setone(work+1);
  networkapi_pre_secure_setzero(work+2);
  work[3] = *xr;
  networkapi_pre_secure_setone(work+4);

  j = 6;
  for(i=31;i>=0;i--)
  {
    while(j >= 0)
    {
      bit = 1&(s[i]>>j);
      swap = bit ^ prevbit;
      prevbit = bit;
      work_cswap(work,swap);
      ladderstep(work);
      j -= 1;
    }
    j = 7;
  }
  *xr = work[1];
  *zr = work[2];
}

int networkapi_scalarmult_secure(
    unsigned char *r, 
    const unsigned char *s, 
    const unsigned char *p
    )
{
  unsigned char e[32];
  unsigned char i;
  for(i=0;i<32;i++) e[i] = s[i];
  e[0] &= 248;
  e[31] &= 127;
  e[31] |= 64; 

  networkapi_pre_secure t;
  networkapi_pre_secure z;
  networkapi_pre_secure_unpack(&t, p);
  mladder(&t, &z, e);
  networkapi_pre_secure_invert(&z, &z);
  networkapi_pre_secure_mul(&t, &t, &z);
  networkapi_pre_secure_pack(r, &t);
  return 0;
}

static const unsigned char base[32] = {9};

int networkapi_scalarmult_secure_base(
    unsigned char *q, 
    const unsigned char *n
    )
{
  return networkapi_scalarmult_secure(q,n,base);
}
